package org.budo.warehouse.logic.consumer.mapping;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.budo.support.lang.util.MapUtil;
import org.budo.support.spring.expression.util.SpelUtil;
import org.budo.warehouse.logic.api.DataEntry;
import org.budo.warehouse.logic.api.DataEntryWrapper;
import org.budo.warehouse.service.entity.FieldMapping;
import org.budo.warehouse.service.entity.Pipeline;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
 * 加入动态列逻辑
 * 
 * @author lmw
 */
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class FieldMappingDataEntry extends DataEntryWrapper {
    private static final Object BEFORE = new Object(), AFTER = new Object();

    private Pipeline pipeline;

    private Boolean originalFields;

    private List<FieldMapping> fieldMappings;

    public FieldMappingDataEntry(DataEntry dataEntry, Pipeline pipeline, Boolean originalFields, List<FieldMapping> fieldMappings) {
        this(pipeline, originalFields, fieldMappings);

        this.setDataEntry(dataEntry);
    }

    @Override
    public Integer getColumnCount(Integer rowIndex) {
        if (!this.getOriginalFields()) {
            return this.getFieldMappings().size();
        }

        return this.getDataEntry().getColumnCount(rowIndex) + this.getFieldMappings().size();
    }

    @Override
    public String getColumnName(Integer rowIndex, Integer columnIndex) {
        if (!this.getOriginalFields()) {
            FieldMapping fieldMapping = this.getFieldMappings().get(columnIndex);
            return fieldMapping.getFieldName();
        }

        Integer columnCount = this.getDataEntry().getColumnCount(rowIndex);
        if (columnIndex >= columnCount) {
            FieldMapping fieldMapping = this.getFieldMappings().get(columnIndex - columnCount);
            return fieldMapping.getFieldName();
        }

        return this.getDataEntry().getColumnName(rowIndex, columnIndex);
    }

    @Override
    public Object getColumnValueAfter(Integer rowIndex, Integer columnIndex) {
        if (!this.getOriginalFields()) {
            FieldMapping fieldMapping = this.getFieldMappings().get(columnIndex);
            return this.mergeFieldValue(rowIndex, fieldMapping, AFTER);
        }

        Integer columnCount = this.getDataEntry().getColumnCount(rowIndex);
        if (columnIndex >= columnCount) {
            FieldMapping fieldMapping = this.getFieldMappings().get(columnIndex - columnCount);
            return this.mergeFieldValue(rowIndex, fieldMapping, AFTER);
        }

        return this.getDataEntry().getColumnValueAfter(rowIndex, columnIndex);
    }

    @Override
    public Object getColumnValueBefore(Integer rowIndex, Integer columnIndex) {
        if (!this.getOriginalFields()) {
            FieldMapping fieldMapping = this.getFieldMappings().get(columnIndex);
            return this.mergeFieldValue(rowIndex, fieldMapping, BEFORE);
        }

        Integer columnCount = this.getDataEntry().getColumnCount(rowIndex);
        if (columnIndex >= columnCount) {
            FieldMapping fieldMapping = this.getFieldMappings().get(columnIndex - columnCount);
            return this.mergeFieldValue(rowIndex, fieldMapping, BEFORE);
        }

        return this.getDataEntry().getColumnValueBefore(rowIndex, columnIndex);
    }

    @Override
    public Boolean getColumnIsKey(Integer rowIndex, Integer columnIndex) {
        if (!this.getOriginalFields()) {
            FieldMapping fieldMapping = this.getFieldMappings().get(columnIndex);
            String fieldValue = fieldMapping.getFieldValue();
            return this.fieldValueIsKey(fieldValue, rowIndex);
        }

        Integer columnCount = this.getDataEntry().getColumnCount(rowIndex);
        if (columnIndex >= columnCount) {
            FieldMapping fieldMapping = this.getFieldMappings().get(columnIndex - columnCount);
            String fieldValue = fieldMapping.getFieldValue();
            return this.fieldValueIsKey(fieldValue, rowIndex);
        }

        return this.getDataEntry().getColumnIsKey(rowIndex, columnIndex);
    }

    /**
     * 判断值表达式是不是主键
     */
    private Boolean fieldValueIsKey(String fieldValue, Integer rowIndex) {
        Integer columnCount = this.getDataEntry().getColumnCount(rowIndex);
        for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
            Boolean columnIsKey = this.getDataEntry().getColumnIsKey(rowIndex, columnIndex);
            if (!columnIsKey) { // 不是主键
                continue;
            }

            // 找到一个主键
            String columnName = this.getDataEntry().getColumnName(rowIndex, columnIndex);
            if (fieldValue.equals("#{" + columnName + "}")) { // 就是这个
                return true;
            }
        }

        return false;
    }

    /**
     * 值表达式计算
     */
    private String mergeFieldValue(Integer rowIndex, FieldMapping fieldMapping, Object beforeAfter) {
        String fieldValue = fieldMapping.getFieldValue();

        if (!fieldValue.startsWith("#{")) { // 必须是表达式
            throw new IllegalArgumentException("#25 fieldValue=" + fieldValue + ", fieldMapping=" + fieldMapping);
        }

        Map<String, Object> _row = new HashMap<String, Object>();
        Integer originalColumnCount = this.getDataEntry().getColumnCount(rowIndex);
        for (int columnIndex = 0; columnIndex < originalColumnCount; columnIndex++) {
            String columnName = this.getDataEntry().getColumnName(rowIndex, columnIndex);

            if (BEFORE.equals(beforeAfter)) {
                Object columnValue = this.getDataEntry().getColumnValueBefore(rowIndex, columnIndex);
                _row.put(columnName, columnValue);
                continue;
            }

            if (AFTER.equals(beforeAfter)) {
                Object columnValue = this.getDataEntry().getColumnValueAfter(rowIndex, columnIndex);
                _row.put(columnName, columnValue);
                continue;
            }

            throw new IllegalArgumentException("#165 beforeAfter=" + beforeAfter + ", fieldMapping=" + fieldMapping);
        }

        Map<String, Object> map = MapUtil.append(_row, //
                "$row", _row);
        return SpelUtil.merge(fieldValue, map);
    }

    @Override
    public String toString() {
        return this.getClass().getSimpleName() + "@" + Integer.toHexString(this.hashCode()) //
                + ", fieldMappings=" + this.getFieldMappings() //
                + ", dataEntry=" + this.getDataEntry() //
                + ", pipeline=" + this.getPipeline();
    }
}